﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Compilador.Auxiliares.AF;
using System.Windows.Forms;
using System.Configuration;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Compilador.Lexico
{
    class AnalizadorLexico
    {
        #region Properties
        private String path;
        private AFD afd;
        private CharBuffer charBuffer;
        private bool dentroComentario;
        #endregion Properties
        /// <summary>
        /// Inicializo lo estrictamente necesario. Creo el buffer a partir del archivo y el AFD a partir de otro archivo
        /// </summary>
        /// <param name="pathArch"></param>
        public AnalizadorLexico(String pathArch)
        {
            try
            {
                this.path = pathArch;
                this.dentroComentario = false;
                long timeStamp = Stopwatch.GetTimestamp();
                this.afd = new AFD("../../Files/archAFD.txt");
                float num = ((float)(Stopwatch.GetTimestamp() - timeStamp)) / ((float)Stopwatch.Frequency);
                String hola = (String.Format("{0} segundos", num.ToString()));
                this.charBuffer = new CharBuffer(pathArch);
            }
            catch (Exception ex)
            {
                Utils.Log.AddError(ex.Message);
                throw new Exception("Error al crear el analizador Lexico" + "\r\n" + ex.Message);
            }
        }
        /// <summary>
        /// Obtener Proximo Token
        /// </summary>
        /// <returns></returns>
        public TokenLexico ObtenerProximoToken()
        {
            TokenLexico componente = new TokenLexico();
            while (!this.charBuffer.FinArchivo)
            {
                //Me va a devolver cualquier caracter menos un \r o \n... esos los saltea.                
                char x = this.charBuffer.ObtenerProximoCaracterValido();
                //MessageBox.Show("caracterValido:"+x);
                componente.AntecedidoPorSeparador = this.charBuffer.HabiaEspacio;
                //Puede haber sido el ultimo caracter del archivo un \r o \n, asi que chequeo
                if (x != char.MinValue)
                {
                    componente.Lexema += x;
                    if (this.afd.TryAvanzar(x))
                    {
                        this.afd.Avanzar(x);

                        //Mientras pueda avanzar (que el AFD lo permita), voy a seguir añadiendo caracteres al lexema.
                        while (this.LookAhead())
                        {
                            x = this.charBuffer.ObtenerProximoChar();
                            componente.Lexema += x;
                            this.afd.Avanzar(x);
                            //MessageBox.Show("Lexema:"+componente.Lexema);
                        }
                        //MessageBox.Show("Termino Token");
                        //Si no es un comentario (Empezo con {)
                        if (!dentroComentario)
                        {
                            //Si el estado actual es final
                            if (this.afd.EstadoActual.EsFinal)
                            {
                                //Si no es un comentario(Empezo con {)
                                if (!(this.afd.EstadoActual.Token == TokenLexico.TokenType.ComentarioApertura))
                                {
                                    componente.Token = this.afd.EstadoActual.Token;
                                    componente.Field = this.charBuffer.Field;
                                    componente.Column = this.charBuffer.Column - componente.Lexema.Length;
                                    //MessageBox.Show("LexemaAdentro:"+componente.Lexema);
                                    //Vuelve a estado inicial de AFD
                                    this.afd.ResetearAFD();
                                    return componente;
                                }
                                else
                                {
                                    //Es un comentario
                                    dentroComentario = true;
                                    componente = new TokenLexico();
                                    //MessageBox.Show("LexemaComentario:"+componente.Lexema);
                                    //Vuelve a estado inicial de AFD
                                    this.afd.ResetearAFD();
                                }
                            }
                            else
                            {
                                this.afd.ResetearAFD();
                                //el lexema no pertenecia a ningun token si el afd no termino en un estado final.
                                componente.Token = TokenLexico.TokenType.Identificador;
                                bool proximoSinSentido = true;
                                while (!CharHelper.esCaracterSalteable(x) && !CharHelper.esNuevaLinea(x) && !CharHelper.esRetornoDeCarro(x) &&!this.afd.TryAvanzar(x) && proximoSinSentido)
                                {
                                    //MessageBox.Show("LEXEMA VERIF:" + x.ToString() + "_ES TOKEN:" + this.afd.TryAvanzar(x));
                                    char auxX = this.charBuffer.PeekProximoChar();
                                    //x = this.charBuffer.ObtenerProximoChar();
                                    if (!CharHelper.esCaracterSalteable(auxX) && !CharHelper.esNuevaLinea(auxX) && !CharHelper.esRetornoDeCarro(auxX) && !this.afd.TryAvanzar(auxX))
                                    {
                                        x = this.charBuffer.ObtenerProximoChar();
                                        componente.Lexema += x;
                                    }
                                    else
                                    {
                                        proximoSinSentido = false;  
                                    }
                                    //MessageBox.Show("Lexema:" + componente.Lexema);
                                    //MessageBox.Show("Lexema:"+componente.Lexema);
                                }
                                componente.Field = this.charBuffer.Field;
                                componente.Column = this.charBuffer.Column - componente.Lexema.Length;
                                //componente.Description = "'" + componente.Lexema + "' no es un lexema valido en el lenguaje LM";
                                //Vuelve a estado inicial de AFD
                                //this.afd.ResetearAFD();

                                var regexNumeroInteger = new Regex("[0-9]+");
                                if (regexNumeroInteger.Match(componente.Lexema).Length == componente.Lexema.Length)
                                {
                                    componente.Token = TokenLexico.TokenType.LiteralInteger;
                                    return componente;
                                }

                                var regexVariable = new Regex("[a-z]");
                                if (regexVariable.IsMatch(componente.Lexema[0].ToString().ToLower()))
                                {
                                    componente.Token = TokenLexico.TokenType.Variable;
                                    return componente;
                                }

                                return componente;
                            }
                        }
                        else
                        {
                            //Si estoy dentro de un comentario, me fijo si el token armado es el de salida.
                            if ((this.afd.EstadoActual.Token == TokenLexico.TokenType.ComentarioClausura))
                            {
                                dentroComentario = false;
                                componente = new TokenLexico();
                            }
                            //Vuelve a estado inicial de AFD
                            this.afd.ResetearAFD();
                        }
                    }
                    else
                    {
                        //Si estoy dentro de un comentario, simplemente lo descarto.
                        if (!dentroComentario)
                        {
                            x = this.charBuffer.ObtenerProximoChar();
                            while (!CharHelper.esCaracterSalteable(x))
                            {
                                componente.Lexema += x;
                                x = this.charBuffer.ObtenerProximoChar();
                            }


                            var regexNumeroInteger = new Regex("[0-9]+");
                            if (regexNumeroInteger.Match(componente.Lexema).Length == componente.Lexema.Length)
                            {
                                componente.Token = TokenLexico.TokenType.LiteralInteger;
                                return componente;
                            }

                            var regexVariable = new Regex("[a-z]");
                            if (regexVariable.IsMatch(componente.Lexema[0].ToString().ToLower()))
                            {
                                componente.Token = TokenLexico.TokenType.Variable;
                                return componente;
                            }

                            //El primer caracter leido no servia para formar ningun lexema
                            componente.Token = TokenLexico.TokenType.Error;
                            componente.Field = this.charBuffer.Field;
                            componente.Column = this.charBuffer.Column - componente.Lexema.Length;
                            componente.Description = "'" + componente.Lexema + "' no es un lexema valido en el lenguaje LM";
                            //Vuelve a estado inicial de AFD
                            this.afd.ResetearAFD();
                            return componente;
                        }
                    }
                }
            }
            //Se devuelve EOF si no se hizo retorno dentro del while.
            componente.Field = this.charBuffer.Field;
            componente.Column = this.charBuffer.Column;
            componente.Token = TokenLexico.TokenType.EOF;
            componente.Lexema = "$";
            //Vuelve a estado inicial de AFD
            this.afd.ResetearAFD();
            return componente;
        }
        /// <summary>
        /// Pispear proximo caracter
        /// </summary>
        /// <returns></returns>
        private bool LookAhead()
        {
            char x = this.charBuffer.PeekProximoChar();
            return this.afd.TryAvanzar(x);
        }
        /// <summary>
        /// Getter Fila Actual
        /// </summary>
        /// <returns></returns>
        public int FieldActual()
        {
            return this.charBuffer.Field;
        }
        /// <summary>
        /// Getter Columna Actual
        /// </summary>
        /// <returns></returns>
        public int ColumnActual()
        {
            return this.charBuffer.Column;
        }
    }
}
